package com.xjoyt.pay.abstracts;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Date;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.time.DateFormatUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;

import com.xjoyt.pay.annotations.LockMethod;
import com.xjoyt.pay.annotations.LockParam;
import com.xjoyt.pay.dto.ExecuteResult;

/**
 * 分布式锁抽象类
 * 
 * @author Administrator
 *
 */
public abstract class AbstractLockAspect {

	protected static final Logger logger = LoggerFactory.getLogger(AbstractLockAspect.class);

	protected abstract StringRedisTemplate getRedisTemplate();

	public Object invoke(ProceedingJoinPoint point, LockMethod annotation) {
		String key = getKey(point, annotation);
		StringRedisTemplate redisTemplate = getRedisTemplate();
		ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();
		String tempValue = DateFormatUtils.format(new Date(), "yyyyMMddHHmmssSSS-") + UUID.randomUUID().toString();
		opsForValue.setIfAbsent(key, tempValue);
		if (!tempValue.equals(opsForValue.get(key))) {
			if (!annotation.block()) {
				return ExecuteResult.fail("服务器处理中，无法重复调用");
			}
		}
		synchronized (key) {
			if (!tempValue.equals(opsForValue.get(key))) {
				if (!annotation.block()) {
					return ExecuteResult.fail("服务器处理中，无法重复调用");
				}

				while (!tempValue.equals(opsForValue.get(key))) {
					try {
						Thread.sleep(Math.abs(annotation.interval()));
					} catch (Exception ex) {
						ex.printStackTrace();
					}
					opsForValue.setIfAbsent(key, tempValue);
				}
				while (!tempValue.equals(opsForValue.get(key))) {
					try {
						Thread.sleep(Math.abs(annotation.interval()));
					} catch (Exception ex) {
						ex.printStackTrace();
					}
					opsForValue.setIfAbsent(key, tempValue);
				}
			}

			if (annotation.timeout() > 0)
				redisTemplate.expire(key, annotation.timeout(), TimeUnit.SECONDS);

			try {
				// 调用
				Object val = point.proceed(point.getArgs());
				return val;
			} catch (Throwable e) {
				e.printStackTrace();
				logger.error("分布式服务器处理异常", e);
				return ExecuteResult.fail("分布式服务器处理异常");
			} finally {
				redisTemplate.delete(key);
			}
		}
	}

	private String getKey(ProceedingJoinPoint point, LockMethod annotation) {
		StringBuilder builder = new StringBuilder();
		if (StringUtils.isBlank(annotation.value())) {
			builder.append("key:lock.");
			builder.append(point.getSignature().getName());
		} else
			builder.append(annotation.value());

		try {
			Class<?>[] par = ((MethodSignature) point.getSignature()).getParameterTypes();
			Method method = point.getTarget().getClass().getMethod(point.getSignature().getName(), par);
			Object[] args = point.getArgs();
			if (args != null) {
				int i = 0;
				for (Object arg : args) {
					i++;
					if (arg == null)
						continue;
					Parameter prameter = method.getParameters()[i - 1];
					if (!prameter.isAnnotationPresent(LockParam.class))
						continue;
					builder.append(".");
					LockParam lockParam = prameter.getAnnotation(LockParam.class);
					if (arg.getClass().isPrimitive() || lockParam.value() == null || lockParam.value().length <= 0) {
						builder.append(arg.toString());
						continue;
					}
					for (String string : lockParam.value()) {
						if (StringUtils.isBlank(string))
							continue;
						String[] split = string.split("\\.");
						builder.append(getFieldName(arg, split, 0));
					}
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return builder.toString();
	}

	private String getFieldName(Object arg, String[] split, int index) {
		if (arg == null)
			return StringUtils.EMPTY;
		if (split.length == 1 && arg instanceof String) {
			return arg.toString();
		}
		if (index + 1 == split.length) {
			Class<?> cls = arg.getClass();
			String name = split[index];
			try {
				Field field = cls.getField(name);
				Object object = field.get(arg);
				return name + "." + (object == null ? StringUtils.EMPTY : object.toString());
			} catch (Exception e) {
				e.printStackTrace();
				return StringUtils.EMPTY;
			}
		}
		Class<?> cls = arg.getClass();
		String name = split[index];
		try {
			Field field = cls.getField(name);
			Object object = field.get(arg);
			return name + "." + getFieldName(object, split, index + 1);
		} catch (Exception e) {
			e.printStackTrace();
			return StringUtils.EMPTY;
		}
	}
}
